package org.truenewx.tnxjee.core.util;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Predicate;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.truenewx.tnxjee.core.Strings;
import org.truenewx.tnxjee.core.util.function.TrPredicate;

/**
 * 操作系统相关工具类
 */
public class OSUtil {

    private OSUtil() {
    }

    public final static String JAVA_HOME = System.getProperty("java.home");

    private final static Map<ProcessHandle, String> PROCESS_COMMAND_LINE_MAPPING = new HashMap<>();
    private static Timer PROCESS_COMMAND_LINE_MAPPING_TIMER;

    public static String currentSystem() {
        String name = System.getProperty("os.name").toLowerCase();
        if (name.contains(Strings.OS_WINDOWS)) {
            return Strings.OS_WINDOWS;
        } else if (name.contains(Strings.OS_ANDROID)) {
            return Strings.OS_ANDROID;
        } else if (name.contains(Strings.OS_IOS)) {
            return Strings.OS_IOS;
        } else if (name.contains(Strings.OS_MAC)) {
            return Strings.OS_MAC;
        }
        return Strings.OS_LINUX;
    }

    public static boolean isInWindows() {
        return Strings.OS_WINDOWS.equals(currentSystem());
    }

    /**
     * 执行指定命令行指令
     *
     * @param command       命令行指令
     * @param resultHandler 结果处理器，为null时不等待结果反馈
     * @return 执行结果
     */
    public static <R> R executeCommand(String command, BiFunction<Integer, String, R> resultHandler) {
        Process process = null;
        try {
            process = Runtime.getRuntime().exec(command);
            if (!command.endsWith(" &")) {
                try {
                    process.waitFor(1, TimeUnit.SECONDS);
                } catch (InterruptedException ignored) {
                }
            }
            return waitResult(process, resultHandler);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            stopProcess(process);
        }
    }

    public static <R> R waitResult(Process process, BiFunction<Integer, String, R> resultHandler) throws IOException {
        int exitValue = -1;
        String result = null;
        try {
            if (process.waitFor(1, TimeUnit.SECONDS)) {
                exitValue = process.exitValue();
                if (exitValue == 0) {
                    result = getResult(process);
                } else {
                    result = getError(process);
                    if (StringUtils.isBlank(result)) {
                        result = getResult(process);
                    }
                }
            } else {
                result = getResult(process);
                if (StringUtils.isEmpty(result)) {
                    result = getError(process);
                    if (StringUtils.isNotEmpty(result)) {
                        exitValue = 1;
                    }
                } else {
                    exitValue = 0;
                }
            }
        } catch (InterruptedException ignored) {
        }
        if (resultHandler != null) {
            return resultHandler.apply(exitValue, result);
        }
        return null;
    }

    public static boolean waitUntilResult(Process process, int timeoutSeconds, BiPredicate<Integer, String> predicate)
            throws IOException {
        long startTime = System.currentTimeMillis();
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream(), getOutputCharset()))) {
            while (true) {
                // 检查进程是否还在运行
                if (!process.isAlive()) {
                    int exitCode = process.exitValue();
                    if (exitCode != 0) {
                        // 读取错误输出
                        String message = getError(process);
                        return predicate.test(exitCode, message);
                    }
                    return false;
                }

                // 非阻塞读取一行
                if (reader.ready()) {
                    String line = reader.readLine();
                    if (predicate.test(0, line)) {
                        return true;
                    }
                }

                // 检查超时
                if (timeoutSeconds > 0) {
                    long dTime = System.currentTimeMillis() - startTime;
                    if (dTime > timeoutSeconds * 1000L) {
                        String message = "Timeout: " + dTime + "ms";
                        return predicate.test(1, message);
                    }
                }

                // 短暂休眠避免CPU占用过高
                ThreadUtil.sleep(100);
            }
        }
    }

    public static void stopProcess(Process process) {
        // 只有在进程还在运行时才需要终止
        if (process != null && process.isAlive()) {
            process.destroy(); // 优先使用正常终止
            try {
                // 给进程一点时间来正常终止
                if (!process.waitFor(1, TimeUnit.SECONDS)) {
                    process.destroyForcibly(); // 如果进程没有及时终止，才强制终止
                }
            } catch (InterruptedException ignored) {
                process.destroyForcibly();
            }
        }
    }

    private static String getResult(Process process) throws IOException {
        try (InputStream in = process.getInputStream()) {
            return IOUtil.readUnblocklyToString(in, getOutputCharset());
        }
    }

    private static String getOutputCharset() {
        String os = currentSystem();
        return Strings.OS_WINDOWS.equals(os) ? Strings.ENCODING_GB18030 : Strings.ENCODING_UTF8;
    }

    private static String getError(Process process) throws IOException {
        try (InputStream in = process.getErrorStream()) {
            return IOUtil.readUnblocklyToString(in, getOutputCharset());
        }
    }

    /**
     * 执行指定命令行指令
     *
     * @param commands      命令行指令集
     * @param dir           执行目录
     * @param resultHandler 结果处理器，为null时不等待结果反馈
     * @return 执行结果
     */
    public static <R> R executeCommand(String[] commands, File dir, BiFunction<Integer, String, R> resultHandler) {
        Process process = null;
        try {
            ProcessBuilder processBuilder = new ProcessBuilder(commands);
            if (dir != null) {
                dir.mkdirs();
                processBuilder.directory(dir);
            }
            process = processBuilder.start();
            if (!Strings.AND.equals(commands[commands.length - 1].trim())) {
                try {
                    process.waitFor(1, TimeUnit.SECONDS);
                } catch (InterruptedException ignored) {
                }
            }
            return waitResult(process, resultHandler);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            stopProcess(process);
        }
    }

    /**
     * 判断指定进程是否存在
     *
     * @param commandPattern      指令样式
     * @param commandLinePatterns 命令行样式
     * @return 指定进程是否存在
     */
    public static boolean existsProcess(String commandPattern, String... commandLinePatterns) {
        return findProcessHandle(commandPattern, commandLinePatterns) != null;
    }

    private static ProcessHandle findProcessHandle(String commandPattern, String... commandLinePatterns) {
        AtomicReference<ProcessHandle> result = new AtomicReference<>();
        forEachProcessHandles(commandPattern, commandLinePatterns, ph -> {
            result.set(ph);
            return false; // 找到一个匹配则退出遍历
        });
        return result.get();
    }

    private static void forEachProcessHandles(String commandPattern, String[] commandLinePatterns,
            Predicate<ProcessHandle> continuePredicate) {
        // 先取得命令样板匹配的所有线程，这一步速度很快，无需考虑提前中止遍历
        List<ProcessHandle> phs = ProcessHandle.allProcesses().filter(ph -> {
            if (ph.isAlive()) {
                Optional<String> optional = ph.info().command();
                if (optional.isPresent()) {
                    String command = optional.get();
                    if (commandPattern.contains(Strings.ASTERISK)) {
                        return StringUtil.wildcardMatch(command, commandPattern);
                    } else { // 进程可能通过已加入PATH环境变量的简短指令执行，需用包含方式匹配
                        return command.contains(commandPattern);
                    }
                }
            }
            return false;
        }).toList();
        // 再尝试匹配命令行样板，这一步可能较慢，需考虑提前中止遍历
        if (ArrayUtils.isNotEmpty(commandLinePatterns)) {
            String os = currentSystem();
            for (ProcessHandle ph : phs) {
                if (matchesCommandLinePatterns(os, ph, commandLinePatterns)) {
                    if (!continuePredicate.test(ph)) {
                        return;
                    }
                }
            }
        } else {
            for (ProcessHandle ph : phs) {
                if (!continuePredicate.test(ph)) {
                    return;
                }
            }
        }
    }

    private static boolean matchesCommandLinePatterns(String os, ProcessHandle ph, String[] commandLinePatterns) {
        if (Strings.OS_WINDOWS.equals(os)) {  // Windows系统无法通过ProcessHandle取得命令行数据
            String cachedCommandLine = PROCESS_COMMAND_LINE_MAPPING.get(ph);
            if (cachedCommandLine != null) {
                if (ph.isAlive()) {
                    return StringUtil.wildcardMatchOneOf(cachedCommandLine, commandLinePatterns);
                } else {
                    PROCESS_COMMAND_LINE_MAPPING.remove(ph);
                }
            }
            String command = "wmic process where ProcessId=" + ph.pid() + " get CommandLine"; // 该指令耗时约为2秒
            return executeCommand(command, (exitValue, result) -> {
                if (exitValue == 0) {
                    String[] lines = result.trim().split(Strings.ENTER);
                    if (lines.length > 1) {
                        for (int i = 1; i < lines.length; i++) {
                            String commandLine = lines[i].trim();
                            cacheProcessCommandLine(ph, commandLine);
                            if (StringUtil.wildcardMatchOneOf(commandLine, commandLinePatterns)) {
                                return true;
                            }
                        }
                    }
                } else {
                    cacheProcessCommandLine(ph, Strings.EMPTY);
                    if (result != null) {
                        LogUtil.warn(OSUtil.class,
                                "====== command execute error:\ncommand: {}\nexitValue: {}\nresult: {}",
                                command, exitValue, result);
                    }
                }
                return false;
            });
        } else {
            Optional<String> commandLine = ph.info().commandLine();
            return commandLine.isPresent() && StringUtil.wildcardMatchOneOf(commandLine.get(), commandLinePatterns);
        }
    }

    private static void cacheProcessCommandLine(ProcessHandle ph, String commandLine) {
        PROCESS_COMMAND_LINE_MAPPING.put(ph, commandLine);
        LogUtil.debug(OSUtil.class, "Cached ProcessHandle(pid={}, commandLine={})", ph.pid(), commandLine);
        if (PROCESS_COMMAND_LINE_MAPPING_TIMER == null) {
            PROCESS_COMMAND_LINE_MAPPING_TIMER = new Timer(true);
            PROCESS_COMMAND_LINE_MAPPING_TIMER.schedule(new TimerTask() {
                @Override
                public void run() {
                    CollectionUtil.removeIf(PROCESS_COMMAND_LINE_MAPPING, (ph, commandLine) -> {
                        if (!ph.isAlive()) {
                            LogUtil.debug(OSUtil.class,
                                    "The cached ProcessHandle(pid={}) is not alive, which was cleaned. The current cached count: {}",
                                    ph.pid(), PROCESS_COMMAND_LINE_MAPPING.size() - 1);
                            return true;
                        }
                        return false;
                    });
                }
            }, 1000, 3000);
        }
    }

    public static int killProcesses(String commandPattern, String... commandLinePatterns) {
        AtomicInteger total = new AtomicInteger(0);
        forEachProcessHandles(commandPattern, commandLinePatterns, ph -> {
            stopProcessHandle(ph);
            total.incrementAndGet();
            return true;
        });
        return total.get();
    }

    public static void stopProcessHandle(ProcessHandle processHandle) {
        // 只有在进程还在运行时才需要终止
        if (processHandle != null && processHandle.isAlive()) {
            if (!processHandle.destroy()) { // 优先使用正常终止
                processHandle.destroyForcibly(); // 如果进程没有及时终止，才强制终止
            }
        }
    }

    public static void loopService(String[] keywords, TrPredicate<String, String, String> predicate) {
        try {
            if (isInWindows()) {
                String command = "cmd /c sc query state= all ";
                if (ArrayUtils.isEmpty(keywords)) {
                    command += "type= service";
                } else if (keywords.length == 1) {
                    command += "| find /i \"" + keywords[0] + Strings.DOUBLE_QUOTES;
                } else {
                    command += "| findstr /i \"" + StringUtils.join(keywords, Strings.SPACE) + Strings.DOUBLE_QUOTES;
                }
                executeCommand(command, (exitValue, result) -> {
                    if (exitValue == 0) {
                        if (result != null) {
                            String[] chunks = result.trim().split("\r\nSERVICE_NAME:");
                            for (String chunk : chunks) {
                                chunk = chunk.trim();
                                if (chunk.startsWith("SERVICE_NAME:")) {
                                    chunk = chunk.substring("SERVICE_NAME:".length()).trim();
                                }
                                int index = chunk.indexOf("\r\nDISPLAY_NAME:");
                                if (index > 0) {
                                    String serviceName = chunk.substring(0, index).trim();
                                    String displayName = chunk.substring(index + "\r\nDISPLAY_NAME:".length()).trim();
                                    index = displayName.indexOf(Strings.ENTER);
                                    if (index >= 0) {
                                        displayName = displayName.substring(0, index).trim();
                                    }
                                    String bin = getServiceBinPath(serviceName);
                                    if (bin != null) {
                                        // 断言未通过则退出循环
                                        if (!predicate.test(serviceName, displayName, bin)) {
                                            return null;
                                        }
                                    }
                                }
                            }
                        }
                    } else {
                        throw new RuntimeException(result);
                    }
                    return null;
                });
            } else {
                // TODO Linux版本暂未实现
            }
        } catch (Exception e) {
            throw ExceptionUtil.toRuntimeException(e);
        }
    }

    public static String getServiceBinPath(String serviceName) {
        if (isInWindows()) {
            return executeCommand("sc qc \"" + serviceName + Strings.DOUBLE_QUOTES, (exitValue, result) -> {
                if (exitValue == 0 && result != null) {
                    int index2 = result.indexOf(" BINARY_PATH_NAME ");
                    result = result.substring(index2 + " BINARY_PATH_NAME ".length()).trim();
                    if (result.startsWith(Strings.COLON)) {
                        return result.substring(1, result.indexOf(Strings.ENTER)).trim();
                    }
                }
                return null;
            });
        } else {
            // TODO Linux版本暂未实现
        }
        return null;
    }

    public static void createService(String serviceName, String displayName, String binPath, String description,
            String... dependentServiceNames) {
        if (isInWindows()) {
            // 检查服务名称合法性
            if (!serviceName.matches("^[a-zA-Z0-9_-]+$")) {
                throw new IllegalArgumentException("Service name contains invalid characters");
            }

            // 构建基本命令
            StringBuilder command = new StringBuilder();
            command.append("sc create \"").append(serviceName)
                    .append("\" binPath=\"").append(binPath)
                    .append("\" displayName=\"").append(displayName)
                    .append("\" start=auto");

            // 添加依赖项
            if (ArrayUtils.isNotEmpty(dependentServiceNames)) {
                command.append(" depend=").append(Strings.DOUBLE_QUOTES)
                        .append(String.join(Strings.SLASH, dependentServiceNames))
                        .append(Strings.DOUBLE_QUOTES);
            }

            // 创建服务
            executeCommand(command.toString(), (exitValue, result) -> {
                if (exitValue != 0) {
                    throw new RuntimeException("Failed to create service: \n" + result);
                }

                // 设置服务描述
                if (StringUtils.isNotBlank(description)) {
                    executeCommand("sc description \"" + serviceName + "\" \"" + description + "\"",
                            (descExitValue, descResult) -> {
                                if (descExitValue > 0) {
                                    LogUtil.warn(OSUtil.class, "Failed to set service description: {}", descResult);
                                }
                                return null;
                            });
                }
                return null;
            });
        } else {
            // TODO Linux版本暂未实现
        }
    }

    public static void updateServiceBinPath(String serviceName, String binPath) {
        if (isInWindows()) {
            executeCommand("sc config \"" + serviceName + "\" binPath=\"" + binPath + Strings.DOUBLE_QUOTES,
                    (exitValue, result) -> {
                        if (exitValue > 0) {
                            throw new RuntimeException("Failed to update service binary path: \n" + result);
                        }
                        return null;
                    });
        } else {
            // TODO Linux版本暂未实现
        }
    }

    public static void startService(String serviceName) {
        if (isInWindows()) {
            executeCommand("sc start \"" + serviceName + Strings.DOUBLE_QUOTES, (exitValue, result) -> {
                if (exitValue > 0) {
                    throw new RuntimeException("Failed to start service: \n" + result);
                }
                return null;
            });
        } else {
            // TODO Linux版本暂未实现
        }
    }

    public static void stopService(String serviceName) {
        if (isInWindows()) {
            executeCommand("sc stop \"" + serviceName + Strings.DOUBLE_QUOTES, (exitValue, result) -> {
                if (exitValue > 0) {
                    // 如果服务已经停止，不视为错误
                    if (result != null && result.contains("already stopped")) {
                        return null;
                    }
                    throw new RuntimeException("Failed to stop service: \n" + result);
                }
                return null;
            });
        } else {
            // TODO Linux版本暂未实现
        }
    }

    public static boolean isRunningService(String serviceName) {
        if (isInWindows()) {
            return executeCommand("sc query \"" + serviceName + Strings.DOUBLE_QUOTES, (exitValue, result) -> {
                if (exitValue == 0 && result != null) {
                    // 检查服务是否存在
                    if (result.contains("SERVICE_NAME: " + serviceName)) {
                        // 检查服务状态是否为 RUNNING (4)
                        return result.contains("STATE") && result.contains("RUNNING");
                    }
                }
                return false;
            });
        } else {
            // TODO Linux版本暂未实现
        }
        return false;
    }

    public static void ensureService(String serviceName, String displayName, String binPath, String description,
            String... dependentServiceNames) {
        String serviceBinPath = getServiceBinPath(serviceName);
        if (serviceBinPath == null) { // 创建服务
            createService(serviceName, displayName, binPath, description, dependentServiceNames);
        } else if (!binPath.equals(serviceBinPath)) { // 修改服务的启动命令
            updateServiceBinPath(serviceName, binPath);
        }
        if (!isRunningService(serviceName)) {
            // 启动服务
            startService(serviceName);
        }
    }

    public static void deleteService(String serviceName) {
        if (isInWindows()) {
            executeCommand("sc delete \"" + serviceName + Strings.DOUBLE_QUOTES, (exitValue, result) -> {
                if (exitValue > 0) {
                    throw new RuntimeException("Failed to delete service: \n" + result);
                }
                return null;
            });
        } else {
            // TODO Linux版本暂未实现
        }
    }

}
